#pragma once
#include "SehnsuchtReaderWriter.hxx"

namespace Sehnsucht { namespace IO { 
	ref_class_decl BasicLoader
	{
	private:

#ifndef REIGN_PHONE // !Phone
 #if _Reign_desktop_support_windows_7
		typedef IWICImagingFactory WicFactory2;
		TinyTypeDecl( WicFactory2 );
 #else
		typedef IWICImagingFactory2 WicFactory2;
		TinyTypeDecl(WicFactory2);
 #endif
#endif // All

		typedef D3D11_INPUT_ELEMENT_DESC DirectXInputElementDesc; TinyTypeDecl(DirectXInputElementDesc);
		typedef ID3D11ShaderResourceView ShaderResourceView; TinyTypeDecl(ShaderResourceView);
		typedef ID3D11GeometryShader GeometryShader; TinyTypeDecl(GeometryShader);
		typedef ID3D11ComputeShader ComputeShader; TinyTypeDecl(ComputeShader);
		typedef ID3D11DomainShader DomainShader; TinyTypeDecl(DomainShader);
		typedef ID3D11VertexShader VertexShader; TinyTypeDecl(VertexShader);
		typedef ID3D11PixelShader PixelShader; TinyTypeDecl(PixelShader);
		typedef ID3D11HullShader HullShader; TinyTypeDecl(HullShader);
		typedef ID3D11InputLayout InputLayout; TinyTypeDecl(InputLayout);
		typedef ID3D11Texture2D Texture2; TinyTypeDecl(Texture2);
		typedef ID3D11Resource Resource; TinyTypeDecl(Resource);
		typedef ID3D11Buffer Buffer; TinyTypeDecl(Buffer);
		typedef BasicReaderWriter Reader;

#if _Reign_desktop_support_windows_7
		typedef ID3D11Device Device3;
		TinyTypeDecl(Device3);
#else
		typedef ID3D11Device1 Device3;
		TinyTypeDecl(Device3);
#endif

#ifdef REIGN_DESKTOP // Desktop
		typedef Reign::Standard::String16 StringArg; TinyTypeDecl( StringArg );
		typedef std::shared_ptr<Reader> UniquePtrReader;
		TinyTypeDecl(UniquePtrReader);
#else // Nondesktop
		typedef Platform::String^ StringArg;
		typedef Reader^ UniquePtrReader;
#endif // All

		typedef Concurrency::task<Void> AsyncAction; TinyTypeDecl(AsyncAction);
		typedef Void Action; 

	private:

		// Members and closed functionality 

#ifndef REIGN_PHONE
		Microsoft::WRL::ComPtr<WicFactory2> m_wicFactory; // Used for creating the resources from memory. 
#endif
		Device3Ptr m_d3dDevice; // Used for creating the resources from memory. 
		UniquePtrReader m_Reader; // Used for reading the resources from files on disk. 

		StringArg GetExtension( 
			_In_ StringArg wszFileName 
			);

		Action CreateTexture(
			_In_ Bool bDecodeAsDDS,
			_In_reads_bytes_(uDataSize) BytePtr pData,
			_In_ UInt32 uDataSize,
			_Outptr_result_nullonfailure_ 
			Texture2Address ppTexture,
			_Outptr_opt_result_nullonfailure_ 
			ShaderResourceViewAddress ppView,
			_In_opt_ StringArg wszDebugName
			);

		Action CreateInputLayout(
			_In_reads_bytes_(uBytecodeSize) BytePtr pBytecode,
			_In_ UInt32 uBytecodeSize,
			_In_reads_opt_(uLayoutDescNumElements) 
			DirectXInputElementDescPtr pLayoutDescElements,
			_In_ UInt32 uLayoutDescNumElements,
			_Outptr_opt_result_nullonfailure_ 
			InputLayoutAddress ppLayout,
			_In_opt_ StringArg wszDebugName
			);

	public_owned_access:

#ifndef REIGN_PHONE
		BasicLoader(
			_In_ Device3Ptr pDevice,
			_In_ WicFactory2Ptr pFactory = nullptr
			);
#else
		BasicLoader(
			_In_ Device3Ptr pDevice
			);
#endif

		// Sync methods 

		Action LoadTexture(
			_In_ StringArg wszFileName, 
			_Outptr_result_nullonfailure_ 
			Texture2Address ppTexture,
			_Outptr_opt_result_nullonfailure_ 
			ShaderResourceViewAddress ppView
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_In_reads_opt_(layoutDescNumElements) 
			D3D11_INPUT_ELEMENT_DESC pLayoutDescElements[],
			_In_ UInt32 uLayoutDescNumElements,
			_Outptr_result_nullonfailure_ 
			VertexShaderAddress ppShader,
			_Outptr_opt_result_nullonfailure_ 
			InputLayoutAddress ppLayout
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_In_reads_opt_(uNumEntries) 
			const D3D11_SO_DECLARATION_ENTRY streamOutDeclaration[],
			_In_ UInt32 uNumEntries,
			_In_reads_opt_(uNumStrides) 
			const UInt32 pBufferStrides[],
			_In_ UInt32 uNumStrides,
			_In_ UInt32 uRasterizedStream,
			_Outptr_result_nullonfailure_ 
			GeometryShaderAddress ppShader
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			HullShaderAddress ppShader
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			DomainShaderAddress ppShader
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			GeometryShaderAddress ppShader
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			PixelShaderAddress ppShader
			);
		Action LoadShader(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			ComputeShaderAddress ppShader
			);

		// Async methods 
	
		AsyncAction LoadTextureAsync(
			_In_ StringArg wszFileName, 
			_Outptr_opt_result_nullonfailure_ 
			Texture2Address ppTexture,
			_Outptr_opt_result_nullonfailure_ 
			ShaderResourceViewAddress ppView
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_In_reads_opt_(layoutDescNumElements) 
			D3D11_INPUT_ELEMENT_DESC pLayoutDescElements[],
			_In_ UInt32 uLayoutDescNumElements,
			_Outptr_result_nullonfailure_ 
			VertexShaderAddress ppShader,
			_Outptr_opt_result_nullonfailure_ 
			InputLayoutAddress ppLayout
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_In_reads_opt_(uNumEntries) const D3D11_SO_DECLARATION_ENTRY pStreamOutDeclaration[],
			_In_ UInt32 uNumEntries,
			_In_reads_opt_(uNumStrides) const UInt32 pBufferStrides[],
			_In_ UInt32 uNumStrides,
			_In_ UInt32 uRasterizedStream,
			_Outptr_result_nullonfailure_ 
			GeometryShaderAddress ppShader
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			HullShaderAddress ppShader
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			DomainShaderAddress ppShader
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			GeometryShaderAddress ppShader
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			PixelShaderAddress ppShader
			);
		AsyncAction LoadShaderAsync(
			_In_ StringArg wszFileName,
			_Outptr_result_nullonfailure_ 
			ComputeShaderAddress ppShader
			);

	public_owned_access:

		template <class TBufferContent>
		REIGN_HINTINLINING static void CreateVertexBuffer(
			_In_ Device3Ptr pDevice,
			_In_ uint32 uNumBufferElements,
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Const_ TBufferContent const *pInitialData
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor = { 0 };
			BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
			BufferDescriptor.BindFlags = D3D11_BIND_VERTEX_BUFFER;
			BufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;

			D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
			BufferInitData.pSysMem = pInitialData;

			ThrowIfFailed(
				//"FluidSimulator: Failed to create vertex buffer.", 
				pDevice->CreateBuffer(
					&BufferDescriptor,
					&BufferInitData,
					ppBuffer
					)
				);
		}

		template <class TBufferContent>
		REIGN_HINTINLINING void CreateVertexBuffer(
			_In_ uint32 uNumBufferElements,
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Const_ TBufferContent const *pInitialData
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor = { 0 };
			BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
			BufferDescriptor.BindFlags = D3D11_BIND_VERTEX_BUFFER;
			BufferDescriptor.Usage = D3D11_USAGE_IMMUTABLE;

			D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
			BufferInitData.pSysMem = pInitialData;

			ThrowIfFailed(
				//"FluidSimulator: Failed to create vertex buffer.", 
				m_d3dDevice->CreateBuffer(
				&BufferDescriptor,
				&BufferInitData,
				ppBuffer
				)
				);
		}

		template <class TBufferContent>
		REIGN_HINTINLINING static void CreateConstantBuffer(
			_In_ Device3Ptr pDevice,
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Const_ _Maybenull_ TBufferContent const *pInitialData = nullptr
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor = { 0 };
			BufferDescriptor.ByteWidth = GetByteSizeOf(TBufferContent);
			BufferDescriptor.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
			BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

			if (pInitialData)
			{
				D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
				BufferInitData.pSysMem = pInitialData;

				ThrowIfFailed(
					//"FluidSimulator: Failed to create constant buffer.", 
					pDevice->CreateBuffer(
						&BufferDescriptor,
						&BufferInitData,
						ppBuffer
						)
					);
			}
			else
			{
				ThrowIfFailed(
					//"FluidSimulator: Failed to create constant buffer.", 
					pDevice->CreateBuffer(
						&BufferDescriptor,
						nullptr,
						ppBuffer
						)
					);
			}
		}

		template <class TBufferContent>
		REIGN_HINTINLINING void CreateConstantBuffer(
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Const_ _Maybenull_ TBufferContent const *pInitialData = nullptr
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor = { 0 };
			BufferDescriptor.ByteWidth = GetByteSizeOf(TBufferContent);
			BufferDescriptor.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
			BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

			if (pInitialData)
			{
				D3D11_SUBRESOURCE_DATA BufferInitData = { 0 };
				BufferInitData.pSysMem = pInitialData;

				ThrowIfFailed(
					//"FluidSimulator: Failed to create constant buffer.", 
					m_d3dDevice->CreateBuffer(
						&BufferDescriptor,
						&BufferInitData,
						ppBuffer
						)
					);
			}
			else
			{
				ThrowIfFailed(
					//"FluidSimulator: Failed to create constant buffer.", 
					m_d3dDevice->CreateBuffer(
						&BufferDescriptor,
						nullptr,
						ppBuffer
						)
					);
			}
		}

		template <class TBufferContent>
		REIGN_HINTINLINING static void CreateStructuredBuffer(
			_In_ Device3Ptr pDevice,
			_In_ uint32 uNumBufferElements,
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Outref_result_nullonfailure_ ID3D11ShaderResourceView **ppSRView,
			_Outref_result_nullonfailure_ ID3D11UnorderedAccessView **ppUAView,
			_Const_ _Maybenull_  TBufferContent const *pInitialData = nullptr
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor;
			Reign::Standard::ZeroValue(&BufferDescriptor);
			BufferDescriptor.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
			BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
			BufferDescriptor.StructureByteStride = GetByteSizeOf(TBufferContent);
			BufferDescriptor.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
			BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

			if (pInitialData)
			{

				D3D11_SUBRESOURCE_DATA BufferInitData;
				Reign::Standard::ZeroValue(&BufferInitData);
				BufferInitData.pSysMem = pInitialData;

				ThrowIfFailed(
					pDevice->CreateBuffer(
						&BufferDescriptor,
						&BufferInitData,
						ppBuffer
						)
					);
			}
			else
			{
				ThrowIfFailed(
					pDevice->CreateBuffer(
						&BufferDescriptor,
						nullptr,
						ppBuffer
						)
					);
			}

			if (ppSRView)
			{
				D3D11_SHADER_RESOURCE_VIEW_DESC BufferSRViewDesc;
				Reign::Standard::ZeroValue(&BufferSRViewDesc);
				BufferSRViewDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
				BufferSRViewDesc.Buffer.ElementWidth = uNumBufferElements;

				ThrowIfFailed(
					pDevice->CreateShaderResourceView(
						(*ppBuffer), (&BufferSRViewDesc),
						(ppSRView)
						)
					);
			}

			if (ppUAView)
			{
				D3D11_UNORDERED_ACCESS_VIEW_DESC BufferUAViewDesc;
				Reign::Standard::ZeroValue(&BufferUAViewDesc);
				BufferUAViewDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
				BufferUAViewDesc.Buffer.NumElements = uNumBufferElements;

				ThrowIfFailed(
					pDevice->CreateUnorderedAccessView(
						(*ppBuffer), (&BufferUAViewDesc),
						(ppUAView)
						)
					);
			}
		}

		template <class TBufferContent>
		REIGN_HINTINLINING void CreateStructuredBuffer(
			_In_ uint32 uNumBufferElements,
			_Outref_result_nullonfailure_ ID3D11Buffer **ppBuffer,
			_Outref_result_nullonfailure_ ID3D11ShaderResourceView **ppSRView,
			_Outref_result_nullonfailure_ ID3D11UnorderedAccessView **ppUAView,
			_Const_ _Maybenull_  TBufferContent const *pInitialData = nullptr
			)
		{
			using namespace Microsoft::WRL;
			using namespace Microsoft;
			using namespace Sehnsucht;

			D3D11_BUFFER_DESC BufferDescriptor;
			Reign::Standard::ZeroValue(&BufferDescriptor);
			BufferDescriptor.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
			BufferDescriptor.ByteWidth = uNumBufferElements * GetByteSizeOf(TBufferContent);
			BufferDescriptor.StructureByteStride = GetByteSizeOf(TBufferContent);
			BufferDescriptor.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
			BufferDescriptor.Usage = D3D11_USAGE_DEFAULT;

			if (pInitialData)
			{

				D3D11_SUBRESOURCE_DATA BufferInitData;
				Reign::Standard::ZeroValue(&BufferInitData);
				BufferInitData.pSysMem = pInitialData;

				ThrowIfFailed(
					m_d3dDevice->CreateBuffer(
						&BufferDescriptor,
						&BufferInitData,
						ppBuffer
						)
					);
			}
			else
			{
				ThrowIfFailed(
					m_d3dDevice->CreateBuffer(
						&BufferDescriptor,
						nullptr,
						ppBuffer
						)
					);
			}

			if (ppSRView)
			{
				D3D11_SHADER_RESOURCE_VIEW_DESC BufferSRViewDesc;
				Reign::Standard::ZeroValue(&BufferSRViewDesc);
				BufferSRViewDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
				BufferSRViewDesc.Buffer.ElementWidth = uNumBufferElements;

				ThrowIfFailed(
					m_d3dDevice->CreateShaderResourceView(
						(*ppBuffer), (&BufferSRViewDesc),
						(ppSRView)
						)
					);
			}

			if (ppUAView)
			{
				D3D11_UNORDERED_ACCESS_VIEW_DESC BufferUAViewDesc;
				Reign::Standard::ZeroValue(&BufferUAViewDesc);
				BufferUAViewDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
				BufferUAViewDesc.Buffer.NumElements = uNumBufferElements;

				ThrowIfFailed(
					m_d3dDevice->CreateUnorderedAccessView(
						(*ppBuffer), (&BufferUAViewDesc),
						(ppUAView)
						)
					);
			}
		}

	}; // Loader

}; }; // Sehnsucht IO